Sync with upstream Ghost v6.58.0 - #21
Open
andreascreten wants to merge 191 commits into
Open
Conversation
…t#29873) no ref - makes the core build consistent with admin/e2e builds
no ref First slice of the settings NiceModal burn-down (follows the router swap TryGhost#29780 and un-portal TryGhost#29789). `ConfirmationModal` and `LimitModal` were NiceModal-shown from **54 call sites across 27 files** — the bulk of settings' remaining NiceModal usage. They now go through a settings-local `ConfirmationProvider`:
ref https://linear.app/ghost/issue/BER-3816 A member import too large to run inside the request is accepted straight away and reported by email once the background job finishes, and when that job failed no email was ever sent. The publisher was told to expect one and nothing ever came, leaving a failed import indistinguishable from one still running. Nobody else found out either, since a queued import runs in-process where a thrown error never reaches the error tracker. The import now runs in three phases and each says what it may do. Gathering what the rows are written against is the only phase allowed to throw, so a throw means nothing was written; writing reports rather than throws, since once a row has committed an import that failed halfway is not one that never ran; and settling up afterwards guards each step on its own, so a failed lookup cannot skip archiving a Stripe price that nothing else will ever clean up. The job then always tells the publisher something, sending a distinct email for an import that could not be completed at all. Whether a row failed for a reason the publisher can act on is not asked. A row that failed is a row that failed, and it goes into the report they receive with the reason beside it. Nothing inspects those errors, which is also why a row's values -- which a database driver inlines into the message of the statement that failed -- cannot reach a log or an error tracker from here. What does reach them is the run itself failing, a cleanup that did not finish, or a notification that could not be sent, none of which carry a row.
no ref - Added an additive Shade `CodeEditor` with lazy CodeMirror loading. - Adopted it only for the header and footer fields on the flagged React tag-details screen. - Added Storybook states for default, focused, error, and disabled behavior. - Portalled Shade autocomplete into a transparent body-level host so suggestions escape clipped editor and accordion containers. - Connected hint and error copy to the editable element and reset shared focus state when a focused editor unmounts. - Added real-browser coverage for editing, saving, autocomplete visibility, positioning stability, and portal paint safety.
ref BER-3863 An investigation into whether a member could reach custom fields through the members API found that they cannot, and never could. Every member-facing surface serialises from a field whitelist that predates the feature, and the member update path drops unknown keys twice before it reaches any custom-field code. Nothing asserted that, so these two tests pin it: a member's own response carries no custom fields, and custom fields a member sends are dropped while the rest of the update still applies, the same way email already behaves on that endpoint.
ref https://linear.app/ghost/issue/BER-3862 A key is typed by hand into member filters, CSV columns, email replacement strings and, before long, themes and editor cards. The hyphen a slug separates with is the one character those readers disagree about: NQL will not parse a hyphen in a property path, and a replacement string matches word characters only, so a hyphenated field reaches the reader as literal text with nothing to say it was never substituted. The convention contradicted itself as well, since the parts of a composite field were already underscored and a name typed with underscores kept them, leaving a single export column carrying both. Underneath that the format had no definition of its own. It was whatever slugify produced, so a rule a growing number of surfaces depend on lived in a helper that answers to URLs and could widen on a version bump. The characters a key may contain are now stated where its readers are known, as an allowlist rather than a list of things to strip out, and the libraries keep only the transliteration and invisible-character stripping they own. Trimming the ends falls out of that rule and makes __proto__ unmintable under every spelling, so it no longer needs reserving alongside constructor. A key is minted once and never changes, so a definition created earlier keeps its old key for good, and rewriting one in place would leave its stored values and every reference to it pointing somewhere else. Those definitions are discarded with their values instead: custom fields sit behind a private flag and have never been released, so only a site that deliberately opted in can hold one, and re-creating the field re-mints the key. The migration measures a key against the shape this release mints rather than looking for a hyphen, because the previous minting passed underscores through untouched and the definitions endpoint shipped four days before the reserved-key guard did.
no ref - Clear populated CodeMirror fields through their own `ControlOrMeta+A` and Backspace key handling. - Wait for the empty document before filling each replacement, then verify the rendered value before saving. - Reuse the expected replacement values in the exact request-body assertions.
This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---| | [@tanstack/react-query](https://tanstack.com/query) ([source](https://redirect.github.com/TanStack/query/tree/HEAD/packages/react-query)) | [`5.101.2` → `5.101.4`](https://renovatebot.com/diffs/npm/@tanstack%2freact-query/5.101.2/5.101.4) |  |  | --- ### Release Notes <details> <summary>TanStack/query (@&TryGhost#8203;tanstack/react-query)</summary> ### [`v5.101.4`](https://redirect.github.com/TanStack/query/blob/HEAD/packages/react-query/CHANGELOG.md#51014) [Compare Source](https://redirect.github.com/TanStack/query/compare/@tanstack/react-query@5.101.3...@tanstack/react-query@5.101.4) ##### Patch Changes - Updated dependencies \[]: - [@&TryGhost#8203;tanstack/query-core](https://redirect.github.com/tanstack/query-core)@​5.101.4 ### [`v5.101.3`](https://redirect.github.com/TanStack/query/blob/HEAD/packages/react-query/CHANGELOG.md#51013) [Compare Source](https://redirect.github.com/TanStack/query/compare/@tanstack/react-query@5.101.2...@tanstack/react-query@5.101.3) ##### Patch Changes - Updated dependencies \[[`7e3c822`](https://redirect.github.com/TanStack/query/commit/7e3c822a10896f41a8f1031c16b85096277af677)]: - [@&TryGhost#8203;tanstack/query-core](https://redirect.github.com/tanstack/query-core)@​5.101.3 </details> --- ### Configuration 📅 **Schedule**: (in timezone Etc/UTC) - Branch creation - Only on Sunday and Saturday (`* * * * 0,6`) - Between 11:00 PM and 11:59 PM, Monday through Friday (`* 23 * * 1-5`) - Between 12:00 AM and 04:59 AM, Monday through Saturday (`* 0-4 * * 1-6`) - Automerge - Only on Sunday and Saturday (`* * * * 0,6`) - Between 11:00 PM and 11:59 PM, Monday through Friday (`* 23 * * 1-5`) - Between 12:00 AM and 04:59 AM, Monday through Saturday (`* 0-4 * * 1-6`) 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://redirect.github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yODAuMyIsInVwZGF0ZWRJblZlciI6IjQzLjI4MC4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119--> Co-authored-by: tryghost-renovate[bot] <269725441+tryghost-renovate[bot]@users.noreply.github.com>
ref https://linear.app/ghost/issue/BER-3861 A value is addressed by key everywhere it matters: the write names the field by key, a segment filter names it by key, and the key is immutable. Carrying the key directly on the values row lets the read and filter paths drop the id-to-key join, so the foreign key now references the field's key column rather than its id.
Two migrations read custom_field_id in their up: the 6.57 leaf-rows migration and the 6.58 underscore-keys reset. The value re-key drops that column, and the idempotency check re-runs every current-major up against the final schema, so each guards its custom_field_id work on the column still being present. A forward install always sees the column, so this changes nothing an install actually applies.
…e holes (TryGhost#29817) ref https://linear.app/ghost/project/moving-routesyaml-off-of-disk-9887873946a1/ - removed comments describing code we already deleted: they point at an eager URL service and a macrotask queue that no longer exist, and one names the wrong place for where `{field}` placeholders get rewritten - removed `unmountRoute` - it had no callers and didn't work anyway, matching `item.path` when a mounted route's path lives at `layer.route.path` - removed the `resourceAlias` fallback and the `filter` key on static pages, neither of which is set anywhere, so both were already fixed values - dropped the query defaults in `fetch-data`, since both callers now pass a complete query and nothing was being defaulted - built the default post query from the API adapter instead of repeating `postsPublic` by hand, so renaming it in `config.ts` can't quietly point the frontend at the Admin endpoint - fixed `Omit<Route, 'path'>` dropping `filter`, `order`, `limit`, `rss` and `contentType` - `Omit` over a union keeps only the shared keys, so the parser could leave one out and still compile - added a `satisfies` check so a resource added without a `QUERY` entry fails the build instead of erroring on every request through that route - fixed three fixtures stubbing `api.posts` where production passes `postsPublic`, and ran the FileStore against the store contract it was the only adapter missing - no behaviour change: every path removed was already unreachable
Hardens the internal-package migration workflow based on its first cold-start use. Requires isolated, freshly based worktrees and fail-fast checks around subtree splitting and importing. Verifies the source checkout is clean, the destination is a linked worktree, and the resulting merge topology preserves and reaches the recorded source history. Adds guidance for assessing public consumers, mapping workspace dependencies under Ghost's strict catalog policy, and minimally integrating legacy CommonJS packages without mixing migration with modernization. Expands verification to cover package resolution, Nx tests, consumer tests, the full build, and release-archive contents.
Removes the permanently enabled `llmsTxt` labs flag now that llms.txt support is generally available. LLMs.txt is still off by default, and can be enabled in settings.
Removes the permanently enabled `explore` Labs flag from Explore Ping now that the feature is generally available. Explore is still configurable in settings.
Updates the settings API response header snapshot after the merged Labs flag removals. Minimal change required to get main green.
…ryGhost#29884) no issue - the config API snapshot embedded the exact labs flag list, and the settings API Stripe test pinned an exact content-length that varies with the size of the labs setting — both churned every time a feature flag was added or removed, causing avoidable snapshot conflicts between PRs - labs is now matched as any object, with an explicit assertion keeping the meaningful coverage: the flags must be a non-empty plain object of booleans; the settings header match now uses anyContentLength like the other settings tests
no issue - the flag has been permanently enabled via GA_FEATURES for a while, so the conditional it gated was dead in practice - the Featurebase widget remains gated by the `featurebase` config, which was already the real on/off switch everywhere else (token endpoint, config serializer)
Removes the permanently enabled `dangerZoneResetAuth` labs flag now that resetting all authentication is generally available. The Danger Zone action is always shown, and the tests no longer need to opt in to the flag.
ref https://linear.app/ghost/issue/BER-3850 Definitions came back in whatever order they happened to be created in, and every surface reads that one list: the settings list, a member's details, the import mapping's targets, the export's columns. A sort_order column now carries the order, added without a backfill because the read tie-breaks on created_at, so a site that never reorders keeps the order it has. The order is set by PUTting the whole collection in the order it should have rather than by editing one field, because where a field sits is a fact about the list and not about the field, so no rank is exposed on a definition anywhere in the API or the domain. That list has to name every definition exactly once, and the rows are locked in key order so two publishers dragging at the same moment cannot deadlock.
ref https://linear.app/ghost/issue/BER-3850 The read that assembles a member's values sorted its rows by field, on the hottest query in the domain: every member of a page, joined against their values. It could never deliver that order to anyone, because the rows become an object keyed by field and an object carries no order, since JSON gives member order no meaning and JavaScript enumerates any key that looks like an array index first. Path ordering stays, so the parts of one composite field still assemble the same way every time.
ref https://linear.app/ghost/issue/BER-3850 The API could take an order but nothing could give it one. The active tab in Settings is now a sortable list, following the newsletters list it borrows its component from. A drag is applied to the whole list rather than the tab it was made in, because a reorder names every definition and the archived ones keep their places around the move. The order just drawn is held on the screen that drew it and rendered from immediately, so letting go is the end of the interaction rather than the start of a round-trip; the response then settles the cached lists directly, since a reorder only succeeds when it named exactly the fields the site has and a refetch would learn nothing. Each cached list is put into the new order rather than replaced, because several live under one key and they do not hold the same fields.
…#29845) no ref *I recommend [reviewing this with whitespace changes disabled](https://github.com/TryGhost/Ghost/pull/29845/changes?w=1).* What ---- Before this change, there was a class called `EmailAnalyticsProviderMailgun`, which basically just exposed a single function that fetched Mailgun events. After this change, there's a function called `fetchMailgunEvents` that does this. Why --- There was no reason for this to be a stateful class. The function is simpler. Test plan --------- - Automated tests - Made sure newsletter opened events still worked with Mailgun. Sent a newsletter, opened it in Gmail, and verified that it appeared in the UI
no issue - removed the `commentsThreads` and `commentsPinning` labs flags now that both features are GA, along with all flag conditionals - deleted the unreachable non-threaded comments UI: the flat reply layout, "Replied to:" snippets, and top-level reply collapsing - made pinned-first ordering and pin moderation unconditional, removing the comments service's labs dependency entirely - bumped comments-ui to 1.6 so already-deployed bundles that still read the flags stay paired with backends that serve them
…ost#29846) ref TryGhost#29845 This change should have no user impact. --- <sub>Stack created with <a href="https://github.com/github/gh-stack">GitHub Stacks CLI</a> • <a href="https://gh.io/stacks-feedback">Give Feedback 💬</a></sub>
towards https://linear.app/ghost/issue/NY-1473 This change should have no user impact. I think this is a useful change on its own, but it'll make [an upcoming change][0] easier. [0]: https://linear.app/ghost/issue/NY-1473 --- <sub>Stack created with <a href="https://github.com/github/gh-stack">GitHub Stacks CLI</a> • <a href="https://gh.io/stacks-feedback">Give Feedback 💬</a></sub>
towards https://linear.app/ghost/issue/NY-1473 This change should have no user impact.
values-service hardcoded the members_custom_fields.key length as 191 while its sibling definitions-service already reads it from the canonical schema. This reads it from the same source so the two services cannot disagree, and neither can drift from the column if the width ever changes.
The static schema restates the field-type and status lists because it cannot import them, guarded only by a keep-in-sync comment. This asserts the schema's isIn lists equal FIELD_TYPE_IDS and FIELD_STATUS, so a divergence fails the build rather than shipping a schema that accepts or rejects the wrong values.
…schema The presentation catalog listed an address's sub-field labels as a loose Record of string to string, so a mistyped or omitted part compiled and silently lost its label. Typing it against the shared Address value schema's keys makes a part added, removed, or renamed upstream a compile error here instead of a missing label at runtime.
The members custom-field filter matches a field and its value on the same leaf row through mongo-knex's $elemMatch operator, pulled in via @tryghost/nql. Pinning the newest patch (nql 0.13.4 / mongo-knex 0.11.2) keeps the filter on the latest fixes rather than the oldest version its range accepts.
The members filter needs a custom field's operator control to live inside that field's own renderer rather than in the filter framework, so the operator can react to the chosen field's type. This adds the segmented select and input primitives a custom renderer composes into one cohesive filter pill, and threads an operator-change callback through the value selector so a renderer can present and own its operator while still reading as a native filter row.
Members can now be filtered and segmented by their custom field values, behind the membersCustomFields flag. A segment names a field by its stable key and matches on its value; the values reach the query through a custom_fields relation that mongo-knex resolves as a correlated subquery, so a custom-field predicate composes with every other member filter. The public key/value/path grammar is rewritten onto the leaf-row columns at one choke point on the Member model, the method every members query routes its filter through, so the same saved segment behaves identically across the list, CSV export, bulk actions, member count, and email audiences without any of those paths wiring it up themselves.
The members filter now lists each custom field as its own entry under a named, searchable section, the way newsletters appear. Choosing a field shows an operator and value control that the field's renderer owns, covering equality and contains matches, is-set and is-not-set for a whole field, and for a composite field like an address the same for an individual part. The chosen predicate serialises to the key/value grammar the backend understands and parses back from it, so a saved segment reopens as the filter that created it. Behind the membersCustomFields flag.
The field picker resolved both its dropdown list and every restored filter's pill from one flattened config map, so a long list of custom fields could not be shortened without dropping fields from resolution and breaking the saved segments that referenced them. A group now carries an optional previewLimit that shortens only what the picker lists, leaving every field resolvable and findable by search, and the members custom fields group opts in at five with the rest behind a Show more.
A recognised (key + value) compound only rejected an unpaired value or path clause. A compound that also carried a clause naming no leaf column had that clause silently dropped instead, leaving a wider match on the key alone. The transformer now fails closed on an unsupported, duplicate, or conflicting clause the same way, so a hand-crafted filter cannot quietly widen its own result.
The composed segments announced generic "Field part", "Operator", and "Value" labels, so two custom field pills on one row were indistinguishable to a screen reader. Each segment's aria-label now carries the field's own name.
A saved segment on an archived field kept filtering the list while its pill vanished, because the picker only knows active fields. Archived fields the current filter references are now hydrated back as read-only pills: the operator and value stay visible as static segments so the filter reads clearly, but the field is gone from the picker so the pill can only be removed. Read-only is a mode on the shade filter primitives, where the segments render as static text through the same chrome, and the operator control now shares one dropdown implementation with them. The picker's overflow copy routes through the pattern's i18n, and Storybook covers the new segments and states. Custom text fields default to contains, and the review tidy-ups ride along: a stable empty-array reference, removal of dead icon branches, dropping a forbidden dark variant, and restored test globals.
Add a guide for testing Ghost on physical devices and with non-default development URLs: HTTPS, subdirectory installs, and a separate Admin hostname.
…yGhost#30024) ref https://linear.app/ghost/issue/MIG-1437/ - adds a testid attribute to the existing file upload dropbox description - adds test to ensure existing behaviour is not changed when the `csvContentImporter` flag is enabled - adds tests for when the `csvContentImporter` is enabled - adds a conditional for the file drop placeholder text
Ghost Admin composes a filter from its own field catalogue and sends NQL, and nothing on either side can tell whether the server honours what was written: admin cannot see the SQL, and the query layer cannot see what admin meant. The only place the two are observable together is the browse endpoint, so these run the NQL admin emits against a real database and assert exactly which members come back. Derived fields are the reason this matters more than it looks. A column filter is its own evidence, but `subscribed` is rewritten to a newsletter relation before it reaches the model, `email_disabled` combines with it to mean something neither column says alone, and a label is a join. Those shapes are agreed between two codebases and pinned nowhere. This lands ahead of the filter engine refactor so the safety net exists before anything moves.
…reen (TryGhost#29962) closes https://linear.app/ghost/issue/NY-1520 Automation workflows could be panned completely out of view, while graph or zoom changes could leave the viewport outside its updated bounds. Centralized viewport measurement, zoom configuration and constraint handling so workflows remain recoverable across canvas, graph and zoom changes. Updated the controls to match the supported 50–100% zoom range and disable actions at their limits.
…ryGhost#30061) closes TryGhost#30040 The file upload allowlist introduced with the file type validation was derived from observed usage and did not include Linux package formats, so sites distributing software could no longer attach `.rpm` or `.deb` files while sibling archive formats like `.tar.gz` continued to work.
…29733) no ref - `free`: kostenlos → kostenloses - `complimentary`: kostenlos → kostenloses (kept identical to `free`, mirroring `de` which uses one word for both) - `Subscription details`: Abo Details → Abo-Details (German orthography requires hyphenated compounds) - `This email address will not be used.`: E-Mail Adresse → E-Mail-Adresse (same hyphenation rule)
…30014) no ref The code injection, webhook, labs YAML editor, feature-toggle confirmation and universal import dialogs were the last `NiceModal.create` dialogs under `settings/advanced`. They now take an `onClose` prop and are rendered by the component that opens them, using `SettingsModal`'s controlled `onClose` path that the routed settings dialogs already use. This removes five of the remaining NiceModal call sites in settings so the provider and dependency can be torn down once the theme and membership dialogs follow.
no ref The invalid-theme, theme-installed, theme-editor confirm and theme-editor input dialogs were the remaining `NiceModal.create` dialogs in the theme settings area. They now render in-tree from the component that opens them: the two `ConfirmationModalContent` dialogs take an `onClose` prop, and the two promise-style editor dialogs take an `onResolve` prop, with the code editor keeping its await-based `requestConfirmation`/`requestInput` helpers via a small pending-request state instead of `NiceModal.show`. The theme code editor's dialogs are rendered as fragment siblings of its dark, backdrop-blurred root so they keep the same theme and stacking they had when NiceModal rendered them at the provider.
ref https://linear.app/ghost/issue/HKG-1914 Ghost's background jobs are moving onto a class-based interface whose transport is swappable, and this package defines the seam every backend implements. A backend extends JobsBackendBase - a frozen start/enqueue/scheduleRecurring/ shutdown contract - and only ever sees the serialised {type, payload} envelope and a single delivery-processor callback, never a live job instance. Keeping the backend blind to job instances is what makes jobs serialisable end to end and lets a durable backend drop in for the in-memory one without touching a call site. The package also exports a backend-agnostic contract test suite, so the in-memory reference backend and any future durable backend are held to the same acceptance, delivery, drain and bounded-shutdown behaviour. It is a private workspace package, matching the other adapter-base-* packages.
ref https://linear.app/ghost/issue/HKG-1914 This is the first working layer on top of the backend contract, and the point at which jobs can be registered and dispatched at all. The class-based jobs service owns everything the transport does not: the handler registry, the JSON envelope boundary, rehydration, cron validation and error reporting. InMemoryJobsBackend is the reference implementation - delivery runs through a bounded fastq queue whose concurrency comes from adapter config, shutdown drains in-flight work within a bounded time, and a recurring schedule is first-write-wins so a re-registration never disturbs one that is already running. A handler failure is reported and rethrown so the backend sees a rejected delivery as a failed one; the in-memory backend defensively logs and drops it, matching the legacy in-process queue with no redelivery yet. Because the backend is selected through adapter config, a durable backend is a drop-in replacement. Boot constructs the service, runs an initially-empty central registration step so there is a single place to wire handlers, then starts delivery.
To quote [ESLint's docs][0]: > If a variable is never reassigned, using the `const` declaration is better. This enables the `prefer-const` ESLint rule and fixes all violations. [0]: https://eslint.org/docs/latest/rules/prefer-const
no ref We had a bunch of code like this in a test: ```js assertExists(result); assert.equal(result, 123); ``` That `assertExists` can be removed. (This also fixes the fact that `assertExists` was imported strangely.)
…30037) closes https://linear.app/ghost/issue/NY-1529 ref ce08414 This adds `stats.last_run_created_at` to each automation, which we'll display in the UI soon. This was built by Claude Opus 5 with the following prompt: > Commit `ce0841435c44a24bd444277cc587e3a18adc052d` adds a "last run created at" stat to the automation browse endpoint. > > I want a new key, `total_run_count`, which is a count of all the runs for that automation. > > * Update `AutomationBrowseResult`. > * Update `AutomationBrowseRow`. > * Update `loadAutomations`. > * Update `buildAutomationBrowseResult`. > * Update `ghost/core/test/e2e-api/admin/automations.test.js`. > * Update `ghost/core/test/unit/server/services/automations/automations-repository.test.ts`. > * Re-generate the snapshots with `UPDATE_SNAPSHOTS=1` and running the necessary tests. > > Use red/green TDD. > > This should be a fairly straightforward change. In addition to unit and E2E tests, I also verified that the data appeared by checking the Network tab of the Firefox devtools:  Co-authored-by: Claude <noreply@anthropic.com>
ref TryGhost#23361 This PR completes the Serbian (`sr`) locale. I'm a native Serbian speaker, and I've filled in all the previously-empty strings across `comments.json`, `ghost.json`, and `portal.json` — including the Serbian plural forms (`{count} month_*` / `{count} year_*`, which need the `_few` paucal form).
no ref This change should have no user impact. [`@ts-ignore` should never be used.][0] This removes all usages. Most of the time, we could simply delete it. Sometimes, we needed to replace it with something. All of the functionality changes were (1) only in tests (2) minor. [0]: https://evanhahn.com/ts-ignore-is-almost-always-the-worst-option/
no ref [`@ts-ignore` should never be used.][0] ESLint now errors if you do. `@ts-nocheck` is also forbidden. `@ts-expect-error` is allowed with a description. No "real" code changes were necessary after this. [0]: https://evanhahn.com/ts-ignore-is-almost-always-the-worst-option/
towards https://linear.app/ghost/issue/PLA-137 This installs Oxfmt in the Ghost repo. It doesn't add any scripts or enforcement, but it enables a workflow where we can run `pnpm exec oxfmt my-file.ts`, which will ease the future transition. --------- Co-authored-by: Steve Larson <9larsons@gmail.com>
ref https://linear.app/ghost/issue/BER-3876/refine-the-field-picker-in-the-members-import-mapping The trigger named the kind of every field it showed, putting "Membership field" under nearly every row to answer a question almost none of them raise. The kind is named now only where a custom field's name is one a native field already has, and with the second line gone the icon leaves its tile, so the control stands at the height every other one does. Building that turned up a bug. The list identified its items by their label, so a custom field named "Name" and the native "Name" were one item to cmdk: both drew as selected, and since the active item is resolved by querying the first match in the DOM, Enter took the native one whichever had been arrowed to. Identity moves to the targets, already namespaced apart as `name` and `custom_fields.name`, and the label moves to keywords — which needs a filter of its own, since cmdk scores an item's value and its keywords as one joined string and a match is otherwise free to run from one into the other. A composite's parts differ only in their last few words, so truncating the label as one string took away the only thing telling those rows apart. The name gives way first now and the part stays whole.
…t#30016) no ref Uses the settings `DialogPortal` from TryGhost#30014. The custom field, welcome email and welcome email customize dialogs were the last `NiceModal.create` dialogs in the membership and email settings areas. They now take an `onClose` prop and are rendered by the component that opens them: the two `SettingsModal` dialogs through the settings dialog portal so they paint above the settings chrome, and the customize dialog directly, since its `EmailDesignModal` is already a portalled Shade `Dialog`. With these converted (plus TryGhost#30014 and TryGhost#30015), no settings dialog is created or shown through NiceModal any more, which clears the way to remove the provider, the dependency and the compatibility bridges.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Syncing fork to upstream release
v6.58.0.